#include <stdio.h>
#include <pthread.h>
#include "../commons/waiter.h"
#include "../commons/errors.h"
#include "../marshaling/querys.h"
#include "aerolineas.h"
#include <signal.h>


//free
int cant_libres=0;
avion_t *libres=NULL;

//distance
int min_distance= INT_MAX;
int generalMinDistance;

pthread_mutex_t mutex;
pthread_cond_t cond;
int ready_count=0;

sem_t * waiter=NULL;

int city_cant;
int* marks=NULL;


aerolinea_t this=NULL;
pthread_t * aviones=NULL;



int the_end=0;

void salirAero(int error){
	if(error != 0)
		switch(error){
			case 1: printf("Error - Insuficiente memoria. \n");break;
			case 2: printf("Error al cargar uno de los archivos. \n");break;
			default: printf("Error inesperado. \n");break;
		}
		
	
	close_main();
	exit(EXIT_FAILURE);
	
}

void waitAviones(aerolinea_t this){
	int i;
	pthread_mutex_lock(&mutex);
	while(ready_count < this->avionesCant){
		pthread_cond_wait(&cond, &mutex);
	}
	ready_count=0;
	pthread_mutex_unlock(&mutex);

}

void print(aerolinea_t this){
	int i;
	for(i=0 ; i<this->avionesCant ; i++){
		add_aeroplane_to_print(this->aviones[i]);
	}
	ready_to_draw();
}

void wakeAviones(aerolinea_t this){
	int i;	
	for(i=0 ; i<this->avionesCant ; i++){
		pthread_cond_signal(&(this->aviones[i])->cond);
	}
}


int minDistance(int n){
	send_min(n);
	min_distance=INT_MAX;
	sem_wait(waiter);
	return get_min();
}

void new_destiny(avion_t avion, aerolinea_t this){
	
	int next;
	int min;
	int min_id;
	int min_path;
	int* path=malloc(city_cant * sizeof(int));
	if(path == NULL) salirAero(MEMORY_ERROR);
	
	int* min_distances=malloc(city_cant * sizeof(int));
	if(min_distances == NULL) salirAero(MEMORY_ERROR);
	
	int** distances=malloc(city_cant * sizeof(int*));
	if(distances == NULL) salirAero(MEMORY_ERROR);
	
	int i, j;
	for(i=0 ; i<city_cant ; i++){
		path[i]=-1;
		min_distances[i]=-1;
		distances[i]=NULL;
	}
	min_distances[avion->ciudadDestinoId]=0;

	do{
		min=INT_MAX;
		min_id=-1;
		min_path=-1;
		for(i=0 ; i<city_cant ; i++){
			if(min_distances[i] != -1){
				if (distances[i]==NULL){
					get_neighbours(&distances[i],i);
				}
				for(j=0 ; j<city_cant ; j++){
					if(min_distances[j]==-1 && distances[i][j]>0 && min_distances[i]+distances[i][j] <= min){
						min=min_distances[i]+distances[i][j];
						min_id=j;
						min_path=i;
					}
				}

			}
		}
		if(min_id!=-1){
			min_distances[min_id]=min;
			path[min_id]=min_path;
		}
	}while(min_id!=-1 && !(!marks[min_id] && needs(min_id, avion->drogasCant, avion->drogas)));
	if(min_id == -1){
		int neigbhour_count=0;
		for(i=0 ; i<city_cant ; i++){
			if(distances[avion->ciudadDestinoId][i]>0)
				neigbhour_count++;
		}
		if(neigbhour_count==0){
			avion->distancia=INT_MAX;
			return;
		}
		int r_num=(rand()%neigbhour_count)+1;
		next=-1;
		for(i=0 ; i<r_num ; i++){
			next++;
			while(distances[avion->ciudadDestinoId][next]<=0)next++;
		}
	}
	else{
		marks[min_id]=1;
		for(next=min_id ; path[next]!=avion->ciudadDestinoId ; next=path[next]);
	}

	avion->distancia=distances[avion->ciudadDestinoId][next];
	avion->ciudadDestinoId=next;

	for(i=0;i<city_cant;i++){
		if(distances[i]!=NULL){
			free(distances[i]);
		}
	}
	free(path);
	free(min_distances);
	free(distances);
}

int destroy_marks(){
	int i;
	for(i=0;i<city_cant;i++){
		marks[i]=0;
	}

}

void close_aviones(){
	the_end=1;
}

void close_aerolinea(int signal){
	if(aviones){
		close_aviones();
		wakeAviones(this);
		int i;
		for(i=0 ; i < this->avionesCant ; i++){
				pthread_join(aviones[i], NULL);
				//pthread_cond_destroy(&this->aviones[i]->cond);
		}
		free(aviones);
	}

	if(libres)
		free(libres);
	if(marks)
		free(marks);
	if(this)
		freeAerolinea(this);

	if(waiter){
		pthread_cond_destroy(&cond);
		pthread_mutex_destroy(&mutex);

		sem_close(waiter);
	}

	if(signal!=SIGPIPE){
		printf("solo una vez!!!!!!\n");
		kill(getppid(), SIGPIPE);
	}

	close_semaphores();
	exit(0);
}

int main(int argc, char *argv[]){
	
	signal(SIGABRT,close_aerolinea);
	signal(SIGPIPE,close_aerolinea);
	signal(SIGQUIT,close_aerolinea);
	signal(SIGTERM,close_aerolinea);

	this = (aerolinea_t) malloc(sizeof(struct st_aerolinea_t));
	
	int ret = loadAerolinea(this, argv[0]);
	if(ret != NO_ERRORS){
		freeAerolinea(this);
		salirAero(ret);	
	}

	pthread_mutex_init(&mutex, NULL);
	pthread_cond_init(&cond, NULL);

	waiter= sem_open(WAITER, 0);
	
	city_cant=get_city_cant();
	marks=malloc(city_cant*sizeof(int));
	if(marks == NULL) salirAero(MEMORY_ERROR);
	
	libres=malloc(sizeof(avion_t) * this->avionesCant);
	if(libres == NULL) salirAero(MEMORY_ERROR);

	aviones=malloc(this->avionesCant* sizeof(pthread_t));
	if(aviones == NULL) salirAero(MEMORY_ERROR);
	int i;
	for(i=0 ; i < this->avionesCant ; i++){
			pthread_create(&aviones[i], NULL, avion, this->aviones[i]);
	}
	
	sem_t* starter=sem_open(STARTER,0);
	sem_post(starter);
	sem_close(starter);

	waitAviones(this);
	while(1){
		wakeAviones(this);
		waitAviones(this);
		destroy_marks();
		for(i=0 ; i< cant_libres ; i++){
			throw_medication(libres[i]);
			if(!null_drugs_demand())
				new_destiny(libres[i], this);
		}
		cant_libres=0;
		print(this);
		wakeAviones(this);
		waitAviones(this);
		generalMinDistance = minDistance(min_distance);
	}
}



